namespace WingBeats.Xhtml

open WingBeats.Xml
open WingBeats.Xml.CreatorParser

[<AutoOpen>]
module Operators =
    let (!!) (name : string) = Node.Placeholder name

type XhtmlElement(strict : bool) =
    
    let _strict = strict

    let empties = Set.ofList ["base";"meta";"link";"hr";"br";"param";"img";"area";"input";"col"]

    let convertAttributes parts =
        let chooseAttrs = function
            | Classes(classes) -> Some(["class", String.concat " " classes])
            | Id(id)           -> Some(["id", id])
            | Attributes(atts) -> Some(atts)
            | _                -> None
        parts |> List.choose chooseAttrs
              |> List.collect id 

    let convertParts parts =
        let getTag atts =
            let isTag = function | Tag(name) -> Some(name) | _ -> None
            let result = List.tryPick isTag atts
            match result with
            | Some(name) -> name
            | None       -> "div"

        let getRepeat atts =
            let isRepeat = function | Repeat(count) -> Some(count) | _ -> None
            let result = List.tryPick isRepeat atts
            match result with
            | Some(count) -> count
            | None        -> 1

        let createTag atts =
            let tag = getTag atts
            let repeat = getRepeat atts
            if empties.Contains tag then
                SelfClosingNode(tag, convertAttributes atts) |> List.replicate repeat
            else
                TagPairNode(tag, convertAttributes atts, []) |> List.replicate repeat
        
        let rec loop items =
            match items with
            | Creator(atts) :: Connector(Child) :: tail   ->
                let tag = getTag atts
                if empties.Contains tag then failwith "Empty tag cannot contain children."
                let repeat = getRepeat atts
                TagPairNode(tag, convertAttributes atts, loop tail) |> List.replicate repeat
            | Creator(atts) :: Connector(Sibling) :: tail ->
                createTag atts @ loop tail
            | Creator(atts) :: []                         ->
                createTag atts
            | [] -> []
            | _ :: _ -> failwith "Invalid creator-connector sequence."

        loop parts

    let parseTagCss (css:string) : (list<string * string> * list<Node>) =
        match parseCreator css with
        | Creator(attrs) :: [] -> 
            convertAttributes attrs, []
        | Creator(attrs) :: Connector(Child) :: tail -> 
            convertAttributes attrs, convertParts tail
        | Connector(Child) :: tail ->
            [], convertParts tail
        | Creator(attrs) :: Connector(_) :: tail ->
            failwith "The CSS creator string for a tag cannot define siblings of that tag. First connector must be child connector."
        | _ -> failwith "Invalid CSS creator string"
        
    let parseAnyCss (css:string) : list<Node> =
        css |> parseCreator |> convertParts

    new () = new XhtmlElement(false)

    member internal this.ContainerNode tagName attrs (inner:#seq<Node>) =
        TagPairNode(tagName, attrs, inner)

    member internal this.ContainerNodeCss tagName creators (inner:#seq<Node>) =
        let attrs, nodes = parseTagCss creators
        this.ContainerNode tagName attrs (Seq.append nodes inner)

    member internal this.EmptyNode tagName attrs =
        SelfClosingNode(tagName, attrs)

    member internal this.EmptyNodeCss tagName creators =
        let attrs, _ = parseTagCss creators
        this.EmptyNode tagName attrs
    
    /// Use this to create an empty element without attributes.
    /// e.Div [] e.Empty
    member this.Empty           = Seq.empty<Node>
    /// Encodes a string as a document node.
    member this.Str         (s) = Text(string s)
    /// Uses a literal string as a document node - does not escape embedded HTML.
    member this.Literal     (s) = LiteralText(s)
    /// Inserts a placeholder ID to be filled-in at rendering time.
    member this.Placeholder (s) = Node.Placeholder(s)
    /// Parses a CSS creator string into one or more nodes.
    member this.Tags        (s) = parseAnyCss s
    /// Generates a node with an arbitrary name.
    member this.Tag tagName creators (inner:#seq<Node>) = this.ContainerNodeCss tagName creators inner

